package com.yanyeori.framework.codegenerator.generator;

import com.baomidou.mybatisplus.annotation.TableId;
import com.yanyeori.framework.core.util.BeanUtil;
import com.yanyeori.framework.core.util.FieldUtil;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

/**
 * 根据实体生成建表sql语句
 *
 * @author chenkuan 2022/6/22
 */
public abstract class GenerateSql {

    private final GenerateSqlConfig config;

    private GenerateSql(GenerateSqlConfig config) {
        this.config = config;
    }

    public static GenerateSqlConfig builder() {
        return new GenerateSqlConfig();
    }

    /**
     * 根据实体类生成建表语句
     *
     * @param classes Class数组
     */
    public String generateSql(Class<?>... classes) {
        StringBuilder sql = new StringBuilder();
        for (Class<?> clazz : classes) {
            String className = clazz.getSimpleName();
            List<Field> fields = BeanUtil.getClassFields(clazz, true);
            StringBuilder column = new StringBuilder();
            String pkColumn = null;
            for (Field f : fields) {
                String fName = f.getName();
                if (!"serialVersionUID".equalsIgnoreCase(fName)) {
                    column.append("\n ").append(escapeCharacter())
                            .append(FieldUtil.convertName(fName, config.fieldPrefix, config.removeFieldPrefix, config.fieldSuffix, config.removeFieldSuffix, config.camelCaseToUnderscore))
                            .append(escapeCharacter()).append(" ")
                            .append(convertFieldType(f.getType().getSimpleName())).append(",");
                    if (pkColumn == null && f.isAnnotationPresent(TableId.class)) {
                        pkColumn = fName;
                    }
                }
            }
            String tableName = FieldUtil.convertName(className, config.tablePrefix, config.removeTablePrefix, config.tableSuffix, config.removeTableSuffix, config.camelCaseToUnderscore);
            String pkSql = "";
            if (pkColumn != null) {
                pkSql = "\n " + pkSql(tableName, pkColumn);
            } else {
                column.deleteCharAt(column.length() - 1);
            }
            sql.append("\n").append(createTableSql(tableName)).append(" (")
                    .append(column).append(pkSql)
                    .append("\n) ").append(dbSetting()).append(";\n");
        }
        System.out.println(sql);
        return sql.toString();
    }

    /**
     * 字段转义符
     */
    public abstract String escapeCharacter();

    /**
     * 字段：java类型转sql类型
     */
    public abstract String convertFieldType(String javaType);

    /**
     * 创建表语句
     */
    public abstract String createTableSql(String tableName);

    /**
     * 创建表语句
     */
    public abstract String dbSetting();

    /**
     * 主键索引语句
     */
    public abstract String pkSql(String tableName, String pkColumn);

    /**
     * MySql
     */
    private static class GenerateMySql extends GenerateSql {

        private GenerateMySql(GenerateSqlConfig config) {
            super(config);
        }

        @Override
        public String escapeCharacter() {
            return "`";
        }

        @Override
        public String convertFieldType(String javaType) {
            String sqlType = "varchar(255)";
            if (javaType.equals("String")) {
                sqlType = "varchar(255)";
            } else if (javaType.equalsIgnoreCase("BigDecimal")
                    || javaType.equalsIgnoreCase("short")
                    || javaType.equalsIgnoreCase("double")
                    || javaType.equalsIgnoreCase("float")) {
                sqlType = "decimal(25,7)";
            } else if (javaType.equals("Date") || javaType.equalsIgnoreCase("Timestamp") || javaType.equals("LocalDateTime")) {
                sqlType = "datetime";
            } else if (javaType.equals("byte[]")) {
                sqlType = "blob";
            } else if (javaType.equals("int") || javaType.equals("Integer") || javaType.equalsIgnoreCase("long")) {
                sqlType = "bigint";
            } else if (javaType.equalsIgnoreCase("boolean")) {
                sqlType = "tinyint";
            }
            return sqlType;
        }

        @Override
        public String createTableSql(String tableName) {
            return "DROP TABLE IF EXISTS `" + tableName + "`;" +
                    "\nCREATE TABLE `" + tableName + "`";
        }

        @Override
        public String dbSetting() {
            return "ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci";
        }

        @Override
        public String pkSql(String tableName, String pkColumn) {
            return "PRIMARY KEY (`" + pkColumn + "`)";
        }
    }

    /**
     * Oracle
     */
    private static class GenerateOracle extends GenerateSql {

        private GenerateOracle(GenerateSqlConfig config) {
            super(config);
        }

        @Override
        public String escapeCharacter() {
            return "\"";
        }

        @Override
        public String convertFieldType(String javaType) {
            String sqlType = "varchar2(255 char)";
            if (javaType.equals("String")) {
                sqlType = "varchar2(255 char)";
            } else if (javaType.equalsIgnoreCase("BigDecimal")
                    || javaType.equalsIgnoreCase("short")
                    || javaType.equalsIgnoreCase("double")
                    || javaType.equalsIgnoreCase("float")) {
                sqlType = "decimal(25,7)";
            } else if (javaType.equals("Date") || javaType.equalsIgnoreCase("Timestamp") || javaType.equals("LocalDateTime")) {
                sqlType = "date";
            } else if (javaType.equals("byte[]")) {
                sqlType = "blob";
            } else if (javaType.equals("int") || javaType.equals("Integer") || javaType.equalsIgnoreCase("long")) {
                sqlType = "NUMBER(19,0)";
            } else if (javaType.equalsIgnoreCase("boolean")) {
                sqlType = "NUMBER(3,0)";
            }
            return sqlType;
        }

        @Override
        public String createTableSql(String tableName) {
            return "CREATE TABLE \"" + tableName + "\"";
        }

        @Override
        public String dbSetting() {
            return "";
        }

        @Override
        public String pkSql(String tableName, String pkColumn) {
            return "CONSTRAINT \"" + tableName + "_PK\" PRIMARY KEY (\"" + pkColumn + "\")";
        }
    }

    /**
     * 数据库类型
     */
    public enum DbType {
        MYSQL,
        ORACLE
    }

    public static class GenerateSqlConfig {
        //数据库类型
        private DbType dbType;
        //表名前缀
        private String tablePrefix;
        //删除实体名前缀
        private String removeTablePrefix;
        //表名后缀
        private String tableSuffix;
        //删除实体名后缀
        private String removeTableSuffix;
        //字段前缀
        private String fieldPrefix;
        //删除字段前缀
        private String removeFieldPrefix;
        //字段后缀
        private String fieldSuffix;
        //删除字段后缀
        private String removeFieldSuffix;
        //是否驼峰转下划线
        private boolean camelCaseToUnderscore = true;

        public GenerateSql build() {
            Objects.requireNonNull(dbType, "DbType not be null");
            switch (dbType) {
                case MYSQL:
                    return new GenerateMySql(this);
                case ORACLE:
                    return new GenerateOracle(this);
                default:
                    throw new IllegalArgumentException("The type of database does not support");
            }
        }

        public GenerateSqlConfig setDbType(DbType dbType) {
            this.dbType = dbType;
            return this;
        }

        public GenerateSqlConfig setTablePrefix(String tablePrefix) {
            this.tablePrefix = tablePrefix;
            return this;
        }

        public GenerateSqlConfig setRemoveTablePrefix(String removeTablePrefix) {
            this.removeTablePrefix = removeTablePrefix;
            return this;
        }

        public GenerateSqlConfig setTableSuffix(String tableSuffix) {
            this.tableSuffix = tableSuffix;
            return this;
        }

        public GenerateSqlConfig setRemoveTableSuffix(String removeTableSuffix) {
            this.removeTableSuffix = removeTableSuffix;
            return this;
        }

        public GenerateSqlConfig setFieldPrefix(String fieldPrefix) {
            this.fieldPrefix = fieldPrefix;
            return this;
        }

        public GenerateSqlConfig setRemoveFieldPrefix(String removeFieldPrefix) {
            this.removeFieldPrefix = removeFieldPrefix;
            return this;
        }

        public GenerateSqlConfig setFieldSuffix(String fieldSuffix) {
            this.fieldSuffix = fieldSuffix;
            return this;
        }

        public GenerateSqlConfig setRemoveFieldSuffix(String removeFieldSuffix) {
            this.removeFieldSuffix = removeFieldSuffix;
            return this;
        }

        public GenerateSqlConfig setCamelCaseToUnderscore(boolean camelCaseToUnderscore) {
            this.camelCaseToUnderscore = camelCaseToUnderscore;
            return this;
        }
    }
}
